Skip to content

feat: Implement true_disp calculation in ctapipe-process + test#2951

Open
koopatroopa787 wants to merge 26 commits into
cta-observatory:mainfrom
koopatroopa787:feature-true-disp-calc
Open

feat: Implement true_disp calculation in ctapipe-process + test#2951
koopatroopa787 wants to merge 26 commits into
cta-observatory:mainfrom
koopatroopa787:feature-true-disp-calc

Conversation

@koopatroopa787
Copy link
Copy Markdown

@koopatroopa787 koopatroopa787 commented Feb 17, 2026

Description

This PR implements the calculation and storage of true_disp parameters (norm and sign) in ctapipe-process for simulated events, resolving #2950.

It calculates true_disp using the telescope pointing and the simulated shower direction, providing a ground truth for training machine learning models.

Changes

  • src/ctapipe/containers.py: Added TrueDispContainer (with norm and sign) and integrated it into ImageParametersContainer.
  • src/ctapipe/tools/process.py: Implemented the true_disp calculation logic in the event processing loop.
  • src/ctapipe/io/datawriter.py: Updated to write the true_disp container to the HDF5 output.
  • src/ctapipe/image/image_processor.py: Fixed a bug where Quantity objects were incorrectly treated as Containers.
  • src/ctapipe/core/traits.py: Fixed a Windows-specific path validation issue.

Verification

  • Added src/ctapipe/tools/tests/test_process_true_disp.py to verify that true_disp_norm and true_disp_sign are correctly calculated and written.
  • Verified manually with pytest.

@koopatroopa787 koopatroopa787 marked this pull request as draft February 17, 2026 17:45
@koopatroopa787 koopatroopa787 marked this pull request as ready for review February 17, 2026 17:58
@yaochengchen
Copy link
Copy Markdown
Contributor

hi @koopatroopa787 , nice timing 🙂 I was actually working on something similar, but you got there first!

I just wanted to share one small thought: since the same true_disp calculation logic already exists in train_disp_reconstructor, it might be worth considering moving the shared computation into a small utility function (e.g. under ctapipe.reco) so both places can call the same implementation. That could help avoid duplication and keep the definitions consistent in the future.

value = get_dataset_path(value.partition("dataset://")[2])
elif url.scheme in ("", "file"):
value = pathlib.Path(url.netloc, url.path)
elif os.name == "nt" and len(url.scheme) == 1:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this unrelated change from this pull request.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hii, On Windows, I found that traitlets (or the ctapipe wrapper) struggles to validate absolute paths. It misinterprets the drive letter (e.g., C:) as a URL scheme (like http:), which causes validation to fail. I have reverted the fix for now to keep this PR strictly focused on the true_disp implementation, but I wanted to mention it in case it's an issue the team would like addressed in a separate, dedicated PR later.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We at the moment explicitly do not support and do not test on windows.

Comment thread src/ctapipe/io/datawriter.py Outdated
true_parameters.concentration,
true_parameters.morphology,
true_parameters.intensity_statistics,
true_parameters.true_disp,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

disp is not an image parameter and does not belong in this group.

Comment thread src/ctapipe/tools/process.py Outdated
if self.should_compute_dl1:
self.process_images(event)

if event.simulation is not None:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not put inline computations inside of the process tool. The CLI is a high-level wrapper around ctapipe functionality and should only contain minimal code, certainly not the computations itself.

Comment thread src/ctapipe/containers.py Outdated
Container for true disp parameters
"""
default_prefix = "true_disp"
norm = Field(nan * u.deg, "True disp norm", unit=u.deg)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We store the predicted disp as a single column, not in separate norm and sign columns.

The true disp should be the same.

Comment thread src/ctapipe/containers.py Outdated
default_factory=CoreParametersContainer,
description="Image direction in the Tilted/Ground Frame",
)
true_disp = Field(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not really an image parameter, so should not be part of this data structure.

It rather should be its own subfiled of the SimulatedCameraContainer

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can also directly use the DispContainer.

@koopatroopa787
Copy link
Copy Markdown
Author

Hey @yaochengchen and @maxnoe , thanks for the help with this!

I'm still getting the hang of the architecture here, so I really appreciate the pointers. I’ve just pushed the fixes we talked about:

Moved the true_disp math out of the tool and into a utility in ctapipe.reco.preprocessing.

Shifted the field to SimulatedCameraContainer and kept it as a single column to match the predicted format.

Updated the tests so they’re all squared away with the new structure.

Also, sorry for the Windows-specific quirks earlier! I'll make sure to test things on a Linux VM next time so I don't run into those pathing issues again.

Let me know if this looks better!

@u.quantity_input(
fov_lon=u.deg, fov_lat=u.deg, hillas_psi=u.rad, hillas_lon=u.deg, hillas_lat=u.deg
)
def calculate_true_disp(fov_lon, fov_lat, hillas_psi, hillas_lon, hillas_lat):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's good to add this utility, but it should then also be used in the training.

Note that we have two different conventions for computing true disp and this should be configurable here (see the code in the training tool / the project_disp option)

@maxnoe
Copy link
Copy Markdown
Member

maxnoe commented Feb 25, 2026

@koopatroopa787 I am seeing files here that simply do not belong and I see you re-introduced a change that we agreed was off-topic here.

I have the feeling that you are using an automated AI tool to author these changes. While we do not have a policy against that, please note that

  • The maintainers spend time reviewing your pull request and this is a bottleneck
  • You the author need to make the first review pass on any code generated by an AI and take responsibility for the code that is being proposed here. You are the author, not the AI

We will close this merge request in case we think we are fighting with a model, not collaborating with a human.

@koopatroopa787
Copy link
Copy Markdown
Author

@koopatroopa787 I am seeing files here that simply do not belong and I see you re-introduced a change that we agreed was off-topic here.

I have the feeling that you are using an automated AI tool to author these changes. While we do not have a policy against that, please note that

  • The maintainers spend time reviewing your pull request and this is a bottleneck
  • You the author need to make the first review pass on any code generated by an AI and take responsibility for the code that is being proposed here. You are the author, not the AI

We will close this merge request in case we think we are fighting with a model, not collaborating with a human.

Hi @maxnoe, sorry about the push. I was moving things over to my Ubuntu server for local testing from my windows and accidentally pushed to the PR. it wasn't supposed to go there.

I also get now why the hdf5tableio.py and tableio.py changes are a problem. i added the isinstance checks as a quick workaround because the docs build kept crashing, and I just wanted to see the rest of the output.It was not the right call from my side.

The actual bug is in datawriter.py. When I wired up the true_disp feature, I passed the value straight into the container list:

containers=[
    event.index,
    true_parameters.hillas,
    event.simulation.tel[tel_id].true_disp,  # wrong, this is an Angle not a Container
]

I was dropping a raw value into that list and that is what broke things.

I'll get that sorted before the next revision. Also, I will take a bit more time to fix the error as I am still trying to understand the complete repo structure, and regarding the AI automation, I do use it for code generation but I use my own logic and understanding before pushing it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants